package org.fujene.memcon;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Logger;

import org.fujene.diskblock.DiskBlock;
import org.fujene.diskblock.PureContentDiskBlock;
import org.fujene.diskblock.SkipPointerDiskBlock;
import org.fujene.diskblock.repli.ReplicateContentDiskBlock;
import org.fujene.diskblock.repli.ReplicateDictionaryDiskBlock;
import org.fujene.diskblock.repli.ReplicateInvertIndexDiskBlock;
import org.fujene.memoryblock.MemoryBlock;
import org.fujene.memoryblock.PureContentBlock;
import org.fujene.memoryblock.SkipPointerBlock;
import org.fujene.memoryblock.repli.ReplicateContentBlock;
import org.fujene.memoryblock.repli.ReplicateDictionaryBlock;
import org.fujene.memoryblock.repli.ReplicateInvertIndexBlock;
import org.fujene.structure.Constants;
import org.fujene.structure.ContentInfo;
import org.fujene.structure.ExtendTerm;
import org.fujene.structure.HashID;
import org.fujene.structure.IndexQueue;
import org.fujene.structure.LookupPointer;

public class ReplicateIndexMemory {
	private static final int MEM_TYPES = Constants.MEMORY_TYPE_KINDS; // そのうち拡張する予定
	private static final int MEM_DICT = Constants.MEMORY_TYPE_DICT;
	private static final int MEM_INVINDEX = Constants.MEMORY_TYPE_INVINDEX;
	private static final int MEM_SKIPPTR = Constants.MEMORY_TYPE_SKIPPTR;
	private static final int MEM_CONT = Constants.MEMORY_TYPE_CONTENTS;
	private static final int MEM_SKIPCONT = Constants.MEMORY_TYPE_SKIPPTRCONT;
	private static final int MEM_PURECONT = Constants.MEMORY_TYPE_PURECONTENT;
	private static final int DEFAULT_OPT_LEN = 1;

	// クラスメンバ ブロックをなんとか表す

	// ブロック本体
	// 各ブロックのサイズ
	private int blockSize = 1048576; // 1MB
	// ブロック数
	private int blockNum = 256;

	private MemoryBlock[] blocks = null;
	private int[] writeBackIDs = null;
	private int nextID = 0;

	private int[] firstBlock = null;
	private int[] lastBlock = null;

	private int contentNum;
	private int supplementNum;
	private int[] contentFirst;
	private int[] contentLast;
	private int[] contentSkipPtrFirst;
	private int[] contentSkipPtrLast;
	private int[] supplementFirst;
	private int[] supplementLast;
	// ディスクとの連絡用
	private IndexDisk disk = null;
	private String dir = null;
	
	private ContentInfo contentInfo = null;
	private Logger logger = null;

	public ReplicateIndexMemory(int blockSize, int blockNum, IndexDisk disk, ContentInfo contentInfo, Logger logger) {
		setBlockSize(blockSize);
		setBlockNum(blockNum);
		this.disk = disk;
		this.dir = disk.getDir();
		this.contentNum = contentInfo.getContentNum();
		this.contentFirst = new int[this.contentNum];
		this.contentLast = new int[this.contentNum];
		this.contentSkipPtrFirst = new int[this.contentNum];
		this.contentSkipPtrLast = new int[this.contentNum];
		this.supplementNum = contentInfo.getSupplementNum();
		this.supplementFirst = new int[this.supplementNum];
		this.supplementLast = new int[this.supplementNum];
		this.contentInfo = contentInfo;
		this.logger = logger;
		initBlock();
	}

	// 一番最初に使おう
	private void initBlock() {
		this.blocks = new MemoryBlock[this.blockNum];
		this.writeBackIDs = new int[MEM_TYPES];

		this.firstBlock = new int[MEM_TYPES];
		this.lastBlock = new int[MEM_TYPES];


		for (int i = 0; i < MEM_TYPES; i++) {
			this.firstBlock[i] = -1;
			this.lastBlock[i] = -1;
			this.writeBackIDs[i] = -1;
		}
		
		for (int i = 0; i < this.contentNum; i++) {
			this.contentFirst[i] = -1;
			this.contentLast[i] = -1;
			this.contentSkipPtrFirst[i] = -1;
			this.contentSkipPtrLast[i] = -1;
		}

		for (int i = 0; i < this.supplementNum; i++) {
			this.supplementFirst[i] = -1;
			this.supplementLast[i] = -1;
		}

		initializeAllocate();
	}

	// 初期アロケート 種類追加時はここをいじる
	private void initializeAllocate() {
		int i;
		allocateBlock(MEM_DICT, 0);
		allocateBlock(MEM_SKIPPTR, 0);
		allocateBlock(MEM_INVINDEX, 0);
		for (i = 0; i < contentNum; i++)
			allocateBlock(MEM_SKIPCONT, i);
		for (i = 0; i < contentNum; i++)
			allocateBlock(MEM_CONT, i);
		for (i = 0; i < supplementNum; i++)
			allocateBlock(MEM_PURECONT, i);
	}

	private int allocateBlock(int type, int index) {
		for (int i = 0; i < blocks.length; i++) {
			if (i == blocks.length - 1 && blocks[i] != null) {
				int mover = -1;
				// メモリの空きがなかったのでディスクに寄せる
				// 優先順: PureContents -> Contents -> Inv. Index -> (→はしばらく凍結)Dictionary ->
				// Skip ptr.
				if (getMemoryAmount(MEM_PURECONT) > 2) {
					mover = getMinimumBlock(MEM_PURECONT);
					saveMemory(mover);
					i = mover;
				}else if (getMemoryAmount(MEM_CONT) > 2) {
					mover = getMinimumBlock(MEM_CONT);
					saveMemory(mover);
					i = mover;
				} else if (getMemoryAmount(MEM_INVINDEX) > 2) {
					mover = getMinimumBlock(MEM_INVINDEX);
					saveMemory(mover);
					i = mover;
					// } else if (getMemoryAmount(MEM_DICT) > 2) {
					// mover = getMinimumBlock(MEM_DICT);
					// saveMemory(mover);
					// i = mover;
					// } else if (getMemoryAmount(MEM_SKIPPTR) > 2) {
					// mover = getMinimumBlock(MEM_SKIPPTR);
					// saveMemory(mover);
					// i = mover;
				}
			}

			if (blocks[i] == null) {
				switch (type) {
				case MEM_DICT:
					blocks[i] = new ReplicateDictionaryBlock(blockSize, nextID++, true);
					if (firstBlock[MEM_DICT] == -1)
						firstBlock[MEM_DICT] = i;
					lastBlock[MEM_DICT] = i;
					blocks[i].setOption(1, contentNum);
					break;
				case MEM_SKIPPTR:
					blocks[i] = new SkipPointerBlock(blockSize, nextID++, true);
					if (firstBlock[MEM_SKIPPTR] == -1)
						firstBlock[MEM_SKIPPTR] = i;
					else
						blocks[lastBlock[MEM_SKIPPTR]].setOption(1, nextID);
					lastBlock[MEM_SKIPPTR] = i;
					blocks[i].setOption(1, -1);
					blocks[i].setOption(2, MEM_SKIPPTR);
					lastBlock[MEM_SKIPPTR] = i;
					break;
				case MEM_SKIPCONT:
					blocks[i] = new SkipPointerBlock(blockSize, nextID++, true);
					if (contentSkipPtrFirst[index] == -1)
						contentSkipPtrFirst[index] = i;
					else
						blocks[contentSkipPtrLast[index]].setOption(1, nextID);
					contentSkipPtrLast[index] = i;
					blocks[i].setOption(1, -1);
					blocks[i].setOption(2, MEM_SKIPCONT);
					break;
				case MEM_INVINDEX:
					blocks[i] = new ReplicateInvertIndexBlock(blockSize, nextID++, true);
					if (firstBlock[MEM_INVINDEX] == -1)
						firstBlock[MEM_INVINDEX] = i;
					lastBlock[MEM_INVINDEX] = i;
					break;
				case MEM_CONT:
					blocks[i] = new ReplicateContentBlock(blockSize, nextID++, true);
					if (contentFirst[index] == -1)
						contentFirst[index] = i;
					contentLast[index] = i;
					blocks[i].setOption(1, contentInfo.getConnectionNum(index));
					break;
				case MEM_PURECONT:
					blocks[i] = new PureContentBlock(blockSize, nextID++, true);
					if (supplementFirst[index] == -1)
						supplementFirst[index] = i;
					supplementLast[index] = i;
					break;
				default:
					// タイプ外
					throw new IndexOutOfBoundsException();
				}

				return i;
			}
		}

		return -1;
	}

	private int getMemoryAmount(int type) {
		int amount = 0;

		for (int i = 0; i < blocks.length; i++) {
			if (blocks[i] != null && blocks[i].getType() == type)
				amount++;
		}

		return amount;
	}

	private int getBlockArrayIndex(int ID) {
		for (int i = 0; i < blocks.length; i++) {
			if (blocks[i] == null)
				continue;
			if (blocks[i].getID() == ID)
				return i;
		}
		// なかった
		return -1;
	}
	
	public ExtendTerm lookupDictionary(LookupPointer ptr, int valueType){
		if (ptr == null || ptr.equals(Constants.emptyPointer))
			return null;
		int arrayIndex = getBlockArrayIndex(ptr.block);
		if (arrayIndex != -1){
			return ((ReplicateDictionaryBlock) blocks[arrayIndex]).getTerm(ptr.point, valueType);
		} else {
			// ディスク探索
			if (disk.isExist(ptr.block)) {
				try {
					ReplicateDictionaryDiskBlock dicDisk = new ReplicateDictionaryDiskBlock(disk
							.getFileMap().get(Integer.valueOf(ptr.block)));
					return dicDisk.getTerm(ptr.point, valueType);
				} catch (Exception e) {
				}
			}
		}

		return null;
		
	}

	// 辞書ポインタだけ読み込み
	public LookupPointer lookupDictionaryNext(LookupPointer ptr) {
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if (arrayIndex != -1) {
			return ((ReplicateDictionaryBlock) blocks[arrayIndex])
					.getNextPointer(ptr.point);
		} else {
			if (disk.isExist(ptr.block)) {
				try {
					ReplicateDictionaryDiskBlock dicDisk = new ReplicateDictionaryDiskBlock(disk
							.getFileMap().get(Integer.valueOf(ptr.block)));
					return dicDisk.getNextPointer(ptr.point);
				} catch (Exception e) {
				}
			}
		}

		return null;
	}

	// 転置インデックスポインタだけ読み込み
	public LookupPointer lookupDictionaryInvIndex(LookupPointer ptr,
			int valueType) {
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if (arrayIndex != -1) {
			return ((ReplicateDictionaryBlock) blocks[arrayIndex])
					.getInvIndexPointer(ptr.point, valueType);
		} else {
			if (disk.isExist(ptr.block)) {
				try {
					ReplicateDictionaryDiskBlock dicDisk = new ReplicateDictionaryDiskBlock(disk
							.getFileMap().get(Integer.valueOf(ptr.block)));
					return dicDisk.getDocIDPointer(valueType, ptr.point);
				} catch (Exception e) {
				}
			}
		}

		return null;
	}

	public long lookupDictionaryFirstID(LookupPointer ptr, int valueType) {
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if (arrayIndex != -1) {
			return ((ReplicateDictionaryBlock) blocks[arrayIndex]).getFirstID(
					ptr.point, valueType);
		} else {
			if (disk.isExist(ptr.block)) {
				try {
					ReplicateDictionaryDiskBlock dicDisk = new ReplicateDictionaryDiskBlock(disk
							.getFileMap().get(Integer.valueOf(ptr.block)));
					return dicDisk.getFirstID(ptr.point, valueType);
				} catch (Exception e) {
				}
			}
		}

		return 0;
	}

	public LookupPointer storeDictionary(String str, LookupPointer next)
			throws UnsupportedEncodingException {
		int dictLast = lastBlock[MEM_DICT];

		ReplicateDictionaryBlock dicBlock = (ReplicateDictionaryBlock) blocks[dictLast];
		// 置き場探し
		int newPtr = dicBlock.getNewPointer(str);
		if (newPtr == -1) {
			// 他をあたる
			int nextBlock = allocateBlock(MEM_DICT, 0);
			if (nextBlock != -1) {
				dictLast = nextBlock;
				dicBlock = (ReplicateDictionaryBlock) blocks[nextBlock];
				newPtr = 0;
			}
		}

		dicBlock.setTerm(newPtr, next, str);
		writeBackIDs[MEM_DICT]++;
		return new LookupPointer(dicBlock.getID(), newPtr);
	}

	// ※追加しない
	public void storeDictionaryPointer(LookupPointer dicPtr,
			LookupPointer next) {
		int arrayIndex = getBlockArrayIndex(dicPtr.block);

		if (arrayIndex != -1) {
			try {
				((ReplicateDictionaryBlock) blocks[arrayIndex]).setNextPointer(
						dicPtr.point, next);
			} catch (Exception e) {
			}
		} else {
			// TODO: ファイル書き換え対応
		}
	}

	// ※上書き
	public void storeDictionaryToInvIndex(LookupPointer dicPtr,
			LookupPointer invIndex, long firstID, int valueType) {
		int arrayIndex = getBlockArrayIndex(dicPtr.block);

		if (arrayIndex != -1) {
			try {
				((ReplicateDictionaryBlock) blocks[arrayIndex]).setInvIndex(
						dicPtr.point, invIndex, firstID, valueType);
			} catch (Exception e) {
			}
		} else {
			// TODO: ファイル書き換え対応
		}
	}

	public LookupPointer lookupSkipPointer(int index) {
		int nextBlockNum = firstBlock[MEM_SKIPPTR];
		SkipPointerBlock skipBlock = null;
		int blockIndex = getBlockArrayIndex(nextBlockNum);
		int findLength = 0;
		int length = 0;

		// ブロック探索
		skipBlock = (SkipPointerBlock) blocks[blockIndex];
		// スキップポインタなし
		if (skipBlock.getLength() == 0)
			return null;

		// インデックス探し
		do {
			length = skipBlock.getLength();
			findLength += length;
			if (findLength >= index)
				break;
			nextBlockNum = skipBlock.getNextblock();
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock) blocks[blockIndex];
		} while (nextBlockNum != -1);

		// そんなになかった
		if (findLength < index || skipBlock == null)
			return null;

		index -= (findLength - length);
		return skipBlock.getPointer(index);
	}

	public LookupPointer lookupContentsSkipPointer(int index, int valueType) {
		int nextBlockNum = contentSkipPtrFirst[valueType];
		SkipPointerBlock skipBlock = null;
		int blockIndex = getBlockArrayIndex(nextBlockNum);
		int findLength = 0;
		int length = 0;

		// ブロック探索
		skipBlock = (SkipPointerBlock) blocks[blockIndex];
		// スキップポインタなし
		if (skipBlock.getLength() == 0)
			return null;

		// インデックス探し
		do {
			length = skipBlock.getLength();
			findLength += length;
			if (findLength >= index)
				break;
			nextBlockNum = skipBlock.getNextblock();
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock) blocks[blockIndex];
		} while (nextBlockNum != -1);

		// そんなになかった
		if (findLength < index || skipBlock == null)
			return null;

		index -= (findLength - length);
		return skipBlock.getPointer(index);
	}
	
	public LookupPointer[] lookupPureContentPointer(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if (arrayIndex != -1) {
			return ((ReplicateContentBlock) blocks[arrayIndex])
					.getSupplementPointer(ptr.point);
		} else {
			if (disk.isExist(ptr.block)) {
				try {
					File desc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contentDisk = new ReplicateContentDiskBlock(
							ptr.block, desc, blockSize, DEFAULT_OPT_LEN);
					return contentDisk.getSupplementPointer(ptr.point);
				} catch (Exception e) {
				}
			}
		}

		return null;
	}

	// 単語スキップポインタのみ
	public int getSkipPointerLength() {
		int nextBlockNum = firstBlock[MEM_SKIPPTR];
		SkipPointerBlock skipBlock = null;
		int blockIndex = -1;
		int findIndex = 0;

		// ブロック探索
		do {
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock) blocks[blockIndex];
			int length = skipBlock.getLength();
			findIndex += length;
			nextBlockNum = skipBlock.getNextblock();
		} while (nextBlockNum != -1);

		return findIndex;
	}

	// 本文スキップポインタのみ
	public int getSkipContentsLength(int type) {
		if(type >= contentNum) return -1;
		int nextBlockNum = contentSkipPtrFirst[type];
		SkipPointerBlock skipBlock = null;
		int blockIndex = -1;
		int findIndex = 0;

		// ブロック探索
		do {
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock) blocks[blockIndex];
			int length = skipBlock.getLength();
			findIndex += length;
			nextBlockNum = skipBlock.getNextblock();
		} while (nextBlockNum != -1);

		return findIndex;
	}

	// 本文スキップポインタ対応
	public void storeSkipPointer(int index, int type, LookupPointer ptr,
			boolean isModify) {
		int nextBlockNum = firstBlock[type];
		int blockIndex = -1;
		int findIndex = 0;
		SkipPointerBlock skipBlock = null;
		SkipPointerBlock setBlock = null;

		// 置き場探し
		do {
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock) blocks[blockIndex];
			int length = skipBlock.getLength();
			findIndex += length;
			if(findIndex - index >= 0){
				setBlock = skipBlock;
				break;
			}
			nextBlockNum = skipBlock.getNextblock();
		} while (nextBlockNum != -1);
		
		if(setBlock != null){
			if(isModify)
				setBlock.setPointer(index, ptr, isModify);
			else{
				byte[] moveVal = null;
				moveVal = setBlock.setPointer(index, ptr, isModify);
				if(moveVal != null){
					do{
						nextBlockNum = skipBlock.getNextblock();
						blockIndex = getBlockArrayIndex(nextBlockNum);
						setBlock = (SkipPointerBlock)blocks[blockIndex];
						moveVal = setBlock.movePointerRep(moveVal);
					}while(moveVal != null);
				}
			}
		}

		writeBackIDs[type]++;
	}

	// デバッグ用 スキップポインタ一覧を出力
	// 本文スキップポインタ対応
	public void showSkipPointer(int type) {
		int nextBlockNum = firstBlock[type];
		SkipPointerBlock skipBlock = null;
		int blockIndex = -1;
		LookupPointer output = null;

		// ブロック探索
		do {
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock) blocks[blockIndex];
			int length = skipBlock.getLength();
			for (int i = 0; i < length; i++) {
				output = skipBlock.getPointer(i);
				int dicBlock = getBlockArrayIndex(output.block);
				ExtendTerm newTerm = ((ReplicateDictionaryBlock) blocks[dicBlock])
						.getTerm(output.point, 0);
				newTerm.skipptr_index = i;
				logger.fine("Term at skip pointer: "
						+ newTerm.toString());
			}
			nextBlockNum = skipBlock.getNextblock();
		} while (nextBlockNum != -1);
	}

	public LookupPointer lookupNextInvIndexExtent(LookupPointer ptr) {
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if (arrayIndex != -1) {
			return ((ReplicateInvertIndexBlock) blocks[arrayIndex])
					.getInvertIndex(ptr.point).getPrevPtr();
		} else {
			if (disk.isExist(ptr.block)) {
				try {
					File desc = disk.getDescriptor(ptr.block);
					ReplicateInvertIndexDiskBlock invDisk = new ReplicateInvertIndexDiskBlock(
							ptr.block, desc, blockSize, DEFAULT_OPT_LEN);
					return invDisk.getInvertIndex(ptr.point).getPrevPtr();
				} catch (Exception e) {
				}
			}
		}

		return null;
	}

	public HashID lookupHashID(LookupPointer ptr) {
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if (arrayIndex != -1) {
			return ((ReplicateInvertIndexBlock) blocks[arrayIndex])
					.getInvertIndex(ptr.point).getHashID();
		} else {
			if (disk.isExist(ptr.block)) {
				try {
					File desc = disk.getDescriptor(ptr.block);
					ReplicateInvertIndexDiskBlock invDisk = new ReplicateInvertIndexDiskBlock(
							ptr.block, desc, blockSize, DEFAULT_OPT_LEN);
					return invDisk.getInvertIndex(ptr.point).getHashID();
				} catch (Exception e) {
				}
			}
		}

		return null;
	}

	public LookupPointer storeInvertIndex(LookupPointer prev,
			long diffID, int termfreq) {
		int invertLast = lastBlock[MEM_INVINDEX];

		ReplicateInvertIndexBlock invBlock = (ReplicateInvertIndexBlock) blocks[invertLast];
		// 置き場探し
		int newPtr = invBlock.getLeft();
		if (newPtr == 0) {
			// 他をあたる
			int nextBlock = allocateBlock(MEM_INVINDEX, 0);
			if (nextBlock != -1) {
				invertLast = nextBlock;
				invBlock = (ReplicateInvertIndexBlock) blocks[nextBlock];
			}
		}
		writeBackIDs[MEM_INVINDEX]++;

		return invBlock.setInvertIndex(prev, diffID, termfreq);
	}

	public String lookupContentsExtent(LookupPointer ptr) {
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if (arrayIndex != -1) {
			return ((ReplicateContentBlock) blocks[arrayIndex]).getContent(ptr.point);
		} else {
			if (disk.isExist(ptr.block)) {
				try {
					File fileDesc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contDisk = new ReplicateContentDiskBlock(ptr.block,
							fileDesc, blockSize, DEFAULT_OPT_LEN);
					return contDisk.getContent(ptr.point);
				} catch (Exception e) {
				}
			}
		}

		return null;
	}
	
	public IndexQueue lookupContentsWithID(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if (arrayIndex != -1) {
			return ((ReplicateContentBlock) blocks[arrayIndex]).getContentWithID(ptr.point);
		} else {
			if (disk.isExist(ptr.block)) {
				try {
					File fileDesc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contDisk = new ReplicateContentDiskBlock(ptr.block,
							fileDesc, blockSize, DEFAULT_OPT_LEN);
					return contDisk.getContentWithID(ptr.point);
				} catch (Exception e) {
				}
			}
		}

		return null;
	}

	public long lookupContentID(LookupPointer ptr) {
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if (arrayIndex != -1) {
			return ((ReplicateContentBlock) blocks[arrayIndex]).getContentID(ptr.point);
		} else {
			if (disk.isExist(ptr.block)) {
				try {
					File fileDesc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contDisk = new ReplicateContentDiskBlock(ptr.block,
							fileDesc, blockSize, DEFAULT_OPT_LEN);
					return contDisk.getContentID(ptr.point);
				} catch (Exception e) {
				}
			}
		}

		return -1;
	}

	public LookupPointer lookupNextContentPointer(LookupPointer ptr) {
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if (arrayIndex != -1) {
			return ((ReplicateContentBlock) blocks[arrayIndex])
					.getNextContentPointer(ptr.point);
		} else {
			if (disk.isExist(ptr.block)) {
				try {
					File fileDesc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contDisk = new ReplicateContentDiskBlock(ptr.block,
							fileDesc, blockSize, DEFAULT_OPT_LEN);
					return contDisk.getNextContentPointer(ptr.point);
				} catch (Exception e) {
				}
			}
		}

		return null;
	}

	public LookupPointer storeContents(String content, long ID, int valueType,
			LookupPointer next) {
		int lastContent = contentLast[valueType];

		ReplicateContentBlock contBlock = (ReplicateContentBlock) blocks[lastContent];
		// 置き場探し
		int newPtr = contBlock.getNewPointer(content);
		if (newPtr == -1) {
			// 他をあたる
			int nextBlock = allocateBlock(MEM_CONT, valueType);
			if (nextBlock != -1) {
				lastContent = nextBlock;
				contBlock = (ReplicateContentBlock) blocks[nextBlock];
				newPtr = 0;
			}
		}

		contBlock.setContent(newPtr, content, ID, next);
		writeBackIDs[MEM_CONT]++;

		return new LookupPointer(contBlock.getID(), newPtr);
	}

	public void storeContentNext(LookupPointer ptr, LookupPointer next) {
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if (arrayIndex != -1) {
			((ReplicateContentBlock) blocks[arrayIndex])
					.setContentPointer(ptr.point, next);
		} else {
			if (disk.isExist(ptr.block)) {
				try {
					// TODO: ディスク書き換え
				} catch (Exception e) {
				}
			}
		}
	}
	
	public String lookupPureContent(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if (arrayIndex != -1) {
			return ((PureContentBlock) blocks[arrayIndex])
					.getContent(ptr.point);
		} else {
			if (disk.isExist(ptr.block)) {
				try {
					File fileDesc = disk.getDescriptor(ptr.block);
					PureContentDiskBlock contDisk = new PureContentDiskBlock(ptr.block,
							fileDesc, blockSize, DEFAULT_OPT_LEN);
					return contDisk.getContent(ptr.point);
				} catch (Exception e) {
				}
			}
		}
		
		return null;
	}
	
	public LookupPointer storePureContent(String str){
		int contentLast = lastBlock[MEM_PURECONT];

		PureContentBlock contBlock = (PureContentBlock) blocks[contentLast];
		// 置き場探し
		int newPtr = contBlock.getNewPointer(str);
		if (newPtr == -1) {
			// 他をあたる
			int nextBlock = allocateBlock(MEM_PURECONT, 0);
			if (nextBlock != -1) {
				contentLast = nextBlock;
				contBlock = (PureContentBlock) blocks[nextBlock];
				newPtr = 0;
			}
		}

		contBlock.setContent(newPtr, str);
		writeBackIDs[MEM_PURECONT]++;

		return new LookupPointer(contBlock.getID(), newPtr);
	}

	public void readMemory() throws Exception {
		// 記録を読み出し
		// コピーコンストラクタ
		SortedMap<Integer, File> fileMap = new TreeMap<Integer, File>(
				disk.getFileMap());
		Set<Integer> fileNums = fileMap.keySet();

		System.out.print("Reading memory... ");
		for (Integer file : fileNums) {
			int ID = file.intValue();
			int type = disk.getFileType(ID);
			int newID;
			switch (type) {
			case MEM_DICT:
				ReplicateDictionaryDiskBlock dicBlock = new ReplicateDictionaryDiskBlock(
						fileMap.get(file));
				newID = getBlockArrayIndex(ID);
				if (newID == -1)
					newID = allocateBlock(MEM_DICT, 0);
				dicBlock.readToMemory((ReplicateDictionaryBlock) blocks[newID]);
				break;

			case MEM_SKIPPTR:
			case MEM_SKIPCONT:
				SkipPointerDiskBlock skipBlock = new SkipPointerDiskBlock(
						fileMap.get(file));
				newID = getBlockArrayIndex(ID);
				if (newID == -1)
					newID = allocateBlock(MEM_SKIPPTR, 0);
				skipBlock.readToMemory((SkipPointerBlock) blocks[newID]);
				break;

			case MEM_INVINDEX:
				ReplicateInvertIndexDiskBlock invBlock = new ReplicateInvertIndexDiskBlock(
						fileMap.get(file));
				newID = getBlockArrayIndex(ID);
				if (newID == -1)
					newID = allocateBlock(MEM_INVINDEX, 0);
				invBlock.readToMemory((ReplicateInvertIndexBlock) blocks[newID]);
				break;

			case MEM_CONT:
				ReplicateContentDiskBlock contBlock = new ReplicateContentDiskBlock(
						fileMap.get(file));
				newID = getBlockArrayIndex(ID);
				if (newID == -1)
					newID = allocateBlock(MEM_CONT, 0);
				contBlock.readToMemory((ReplicateContentBlock) blocks[newID]);
				break;

			case MEM_PURECONT:
				PureContentDiskBlock pureBlock = new PureContentDiskBlock(
						fileMap.get(file));
				newID = getBlockArrayIndex(ID);
				if (newID == -1)
					newID = allocateBlock(MEM_CONT, 0);
				pureBlock.readToMemory((ReplicateContentBlock) blocks[newID]);
				break;

			default:
				break;
			}
		}

		// 読み込んだブロックは消去
		for (int i = 0; i < blocks.length; i++) {
			if (blocks[i] != null) {
				disk.dropFile(blocks[i].getID());
			}
		}

		disk.reloadFileList();

		System.out.println("Done.");
	}

	public void saveMemory(int index) {
		if (blocks[index] != null) {
			// フラグを立てておく
			blocks[index].setAboutToFlush(true);
			// 既存のファイルがあれば消去
			disk.dropFile(blocks[index].getID());
			switch (blocks[index].getType()) {
			case MEM_DICT:
				ReplicateDictionaryDiskBlock dicBlock = new ReplicateDictionaryDiskBlock(
						(ReplicateDictionaryBlock) blocks[index], dir);
				dicBlock.save();
				break;

			case MEM_SKIPPTR:
			case MEM_SKIPCONT:
				SkipPointerDiskBlock skipBlock = new SkipPointerDiskBlock(
						(SkipPointerBlock) blocks[index], dir);
				skipBlock.save();
				break;

			case MEM_INVINDEX:
				ReplicateInvertIndexDiskBlock invBlock = new ReplicateInvertIndexDiskBlock(
						(ReplicateInvertIndexBlock) blocks[index], dir);
				invBlock.save();
				break;

			case MEM_CONT:
				ReplicateContentDiskBlock contBlock = new ReplicateContentDiskBlock(
						(ReplicateContentBlock) blocks[index], dir);
				contBlock.save();
				break;

			case MEM_PURECONT:
				PureContentDiskBlock pureBlock = new PureContentDiskBlock(
						(PureContentBlock) blocks[index], dir);
				pureBlock.save();
				break;

			default:
				break;
			}
			// ブロック消去
			blocks[index] = null;
			disk.reloadFileList();
		}
	}

	public void saveMemories() {
		// 今までの記録を保存
		System.out.print("Writing to memory... ");
		for (int i = 0; i < blocks.length; i++) {
			saveMemory(i);
		}

		System.out.println("Done.");
	}

	public int getMinimumBlock(int type) {
		int minimum = Integer.MAX_VALUE;
		int minimumArray = -1;
		for (int i = 0; i < blocks.length; i++) {
			if (blocks[i] != null && blocks[i].getType() == type
					&& blocks[i].getID() < minimum) {
				minimum = blocks[i].getID();
				minimumArray = i;
			}
		}

		return minimumArray;
	}

	// デバッグ用
	public void showMemoryElapsed() {
		System.out.println("--  Disk  --");
		Set<Integer> keys = disk.getFileMap().keySet();
		for (Integer diskID : keys) {
			DiskBlock diskBlock;
			try {
				File desc = disk.getFileMap().get(diskID);
				diskBlock = new DiskBlock(desc) {
					@Override
					public void readToMemory(MemoryBlock memBlock) {
						throw new NoSuchMethodError("Unimplemented method");
					}

					@Override
					public byte[] getData() {
						throw new NoSuchMethodError("Unimplemented method");
					}
				};
				System.out.println(diskID + ": " + desc.length() + " " + diskBlock.getTypeStr());
			} catch (Exception e) {
			}
		}

		System.out.println("-- Memory --");
		for (int i = 0; i < blocks.length; i++) {
			if (blocks[i] != null) {
				System.out.println(blocks[i].getID() + ": "
						+ blocks[i].getUsed() + " "
						+ blocks[i].getClassName());
			}
		}
	}

	// デバッグ用
	public void showMemoryType() {
		for (int i = 0; i < blocks.length; i++) {
			if (blocks[i] != null) {
				System.out.println(i + ": " + blocks[i].getClassName());
			}
		}
	}

	public void setBlockSize(int blockSize) {
		this.blockSize = blockSize;
	}

	public void setBlockNum(int blockNum) {
		this.blockNum = blockNum;
	}

	public int getBlockSize() {
		return blockSize;
	}

	public int getBlockNum() {
		return blockNum;
	}

	public int getContentCount() throws Exception {
		int result = 0;
		// メモリ
		for (int i = 0; i < blocks.length; i++) {
			if (blocks[i] != null && blocks[i] instanceof ReplicateContentBlock) {
				result += ((ReplicateContentBlock) blocks[i]).getCount();
			}
		}

		// ディスク
		Collection<File> files = disk.getFileMap().values();
		for (File file : files) {
			ReplicateContentDiskBlock blk = new ReplicateContentDiskBlock(file);

			if (blk.getType() == MEM_CONT) {
				result += blk.getCount();
			}
		}

		return result;
	}

	public int getTermCount() throws Exception {
		int result = 0;
		for (int i = 0; i < blocks.length; i++) {
			if (blocks[i] != null && blocks[i] instanceof ReplicateDictionaryBlock) {
				int count = ((ReplicateDictionaryBlock) blocks[i]).getTermCount();
				result += count;
			}
		}

		// ディスク
		Collection<File> files = disk.getFileMap().values();
		for (File file : files) {
			ReplicateDictionaryDiskBlock blk = new ReplicateDictionaryDiskBlock(file);

			if (blk.getType() == MEM_DICT) {
				result += blk.getTermCount();
			}
		}

		return result;
	}
}
